--[[
Gemböbble
Copyright (c) 2010-2011 Carl Ådahl

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
]]
Ball = 
{ 	
	-- "Public static"
	colorCount = 8
}

-- "Private static" 
local moveSpeed = 8
local maxFallHSpeed = 2
local maxFallVSpeed = 0
local fallGravity = 0.05
local ballsImageData = love.image.newImageData("img/balls64.png")
local imgSize = 96
local images = {}
local screenLeft = cfg.ballRadius
local screenRight = love.graphics.getWidth() - cfg.ballRadius
local sfx = 
		{ 
			hit = sound.effect("snd/ball_hit.mp3", "snd/ball_hit2.mp3", "snd/ball_hit3.mp3", "snd/ball_hit4.mp3") 
		}


-- Static initialization
for i=1,Ball.colorCount do
	local tempData = love.image.newImageData(imgSize, imgSize)
	tempData:paste(ballsImageData, 0, 0, (i-1)*imgSize, 0, imgSize, imgSize)
	images[i] = love.graphics.newImage(tempData)
end

function Ball.new(x, y, color)

	local s = Actor.new("ball")
	local blendMode = "alpha"
	local xv = 0
	local yv = 0
	local scale = 1
	local image = images[color]
	local shadow
	local shine
	
	function s.init()
		s.color = color
		shadow = s.attach(world.spawn(BallShadow.new(s)))
--		shine = s.attach(world.spawn(BallShine.new(s)))
	end
	
	function s.getX()
		return x
	end

	function s.getY()
		return y
	end

	function s.draw()
		love.graphics.pushMaterial()
		love.graphics.setBlendMode(blendMode)
		love.graphics.draw(	image, 
							x,
							y,
							0,
							scale,
							scale,
							image:getWidth()/2,
							image:getHeight()/2 )		
		love.graphics.popMaterial()
	end

	-- State: standby	
	-- Exit: rollin
	-- Desc: Ball is standing by off the top of the screen.
    function s.states.standby(offset)
    	y = y-offset
    	
    	-- Wait for the background. When it's beginning to scroll we should also move.
		s.waitUntil(background, "loadingMap")
		
		-- Move on-screen now.
    	s.enterState("rollin", offset) 
    end

	-- State: rollin	
	-- From: standby
	-- To: EXTERNAL
	-- Desc: Rolling in from the top. This is synchronized with the background. (though not explicitly)
	function s.states.rollin(offset)
		local fromY = y
		local toY = y + offset
		
		-- Roll in over 2 seconds
		s.doFor(120, function(localTime)
			y = math.lerp(fromY, toY, math.smoothstep(0,1,math.smoothstep(0, 120, localTime)))
    	end)
    end

	-- State: moving	
	-- Exit: dying, sticking
	-- Desc: The ball has been fired and is now moving in the map.
	function s.states.moving(angle)
        local a = math.rad(angle + 270)
        local speed = moveSpeed--0
        local acc = 0.7
	    xv = math.cos(a)
	    yv = math.sin(a)

		while s.update() do
			-- Apply acceleration and velocity.
			--speed = math.min(moveSpeed, speed + acc * s.dt)
            x = x + xv * speed * s.dt
            y = y + yv * speed * s.dt

			-- Bounce against walls.
		    if x < screenLeft then
				sfx.hit()		
		        x = 2 * screenLeft - x
		        xv = -xv
		    elseif x >= screenRight then
				sfx.hit()		
		        x = 2 * screenRight - x
		        xv = -xv
		    end

			-- If we hit the ceiling, or come within a certain radius of another ball,
			-- we should either stick into place or clear a group of balls.
			if y < cfg.ballRadius or map.getWithinRadius(s, "ball", cfg.ballSize * 0.9) then
				-- Find the nearest free cell.
				local fi, fj
				fi,fj,fx,fy = map.findFreeCell(x, y)

				-- Stick us on the map.
				map.setBall(fi, fj, s)
				sfx.hit()		

				-- Do we have 3 or more of the same color? Then remove them.				
		        local matches = map.handleMatches(fi,fj)
				if matches then
					world.spawn(StickEffect.new(x, y, s))
					world.spawn(FlareEffect.new(fx, fy, s))
				    local count = 1
				    for ball, ij in pairs(matches) do
				        map.setBall(ij[1], ij[2], nil)
				        ball.enterState("dying", count * 5 )
				        count = count + 1
				    end
				else
					-- Nah, it's just stuck. Leave it there.
					world.spawn(StickEffect.new(x, y, s))
					s.enterState("sticking", fx, fy)
					s.wait()
				end
			end
		end
    end

	-- State: sticking	
	-- Exit: stuck
	-- Desc: Will ease-into a supplied position. Used to adjust collisions smoothly instead of just snapping.
	function s.states.sticking(fx,fy)
		local stickTime = 5
		local xv = (fx-x)/stickTime
		local yv = (fy-y)/stickTime

		s.doFor(stickTime, function()
			x = x + xv * s.dt
			y = y + yv * s.dt
		end)
		
		x = fx
		y = fy
		s.enterState("stuck")
	end

	-- State: stuck
	-- Exit: idle
	-- Desc: The ball has collided and is now stuck to the map. It just goes into idle since nothing needs to be done.
    function s.states.stuck()
    	-- Purposely exiting here to avoid the overhead of calling all stuck balls every frame.
    end
    
	-- State: dying	
	-- Exit: REMOVE
	-- Desc: Will remove this ball from the map, send it flying+shrinking and then die.
    function s.states.dying(delay)
		s.setDrawPriority(-2)
		blendMode = "alpha"

--		world.spawn(FlareEffect.new(x, y, s))
		
		-- Remove the shine object
   	--	world.remove(shine)
   		shine = nil

      	local scalev = -math.random(2000000)/50000000-0.01	-- scale velocity for shrinking
		local a = math.rad(math.random(3600000))/10000		-- ball will fly at a random angle
      	local xv = 2 * math.cos(a)							
      	local yv = 2 * math.sin(a)

      	while s.update() do
      		-- Update position and scale
        	x = x + xv * s.dt
        	y = y + yv * s.dt
        	scale = scale + scalev * s.dt
        	shadow.scale = scale
        	
        	-- Remove the ball when it has shrunk enough.
        	if 1/scale <= 0.05 then
        		world.remove(s)
        		world.remove(shadow)
        	end
      	end
    end

	s.init()
	return s	
end

